﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Bookvar.ObjectModel;
using Bookvar.ServiceModel;

namespace Bookvar.UI
{
    /// <summary>
    /// This service provides functionality to change the view of the mind map. This includes zooming,
    /// centering topics, etc.
    /// </summary>
    public class ViewService : IViewService
    {
        private Point? startDragPoint;
        private readonly MindMapPanel mindmapPanel;
        private readonly RangeBase range;
        private readonly ScrollViewer scrollViewer;

        #region Animation

        public static readonly DependencyProperty HorizontalOffsetProperty =
            DependencyProperty.Register( "AnimatableHorizontalOffset", typeof ( double ), typeof ( ScrollViewer ),
                                         new UIPropertyMetadata( default( double ), ChangeHorizontalOffset ) );

        public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register( "AnimatableVerticalOffset", typeof ( double ), typeof ( ScrollViewer ),
                                         new UIPropertyMetadata( default( double ), ChangeVerticalOffset ) );

        private static void ChangeHorizontalOffset( DependencyObject sender, DependencyPropertyChangedEventArgs args )
        {
            ScrollViewer scrollViewer = (ScrollViewer) sender;
            scrollViewer.ScrollToHorizontalOffset( (double) args.NewValue );
        }

        private static void ChangeVerticalOffset( DependencyObject sender, DependencyPropertyChangedEventArgs args )
        {
            ScrollViewer scrollViewer = (ScrollViewer) sender;
            scrollViewer.ScrollToVerticalOffset( (double) args.NewValue );
        }

        #endregion

        public bool IsDragging
        {
            get { return startDragPoint.HasValue; }
        }

        public ViewService( MindMapPanel mindmapPanel, RangeBase range, ScrollViewer scrollViewer )
        {
            this.mindmapPanel = mindmapPanel;
            this.range = range;
            this.scrollViewer = scrollViewer;

            InitializeZoomTransform();
        }

        private void InitializeZoomTransform()
        {
            ScaleTransform zoomTransform = new ScaleTransform();
            Binding xBinding = new Binding( "Value" );
            xBinding.Source = range;
            BindingOperations.SetBinding( zoomTransform, ScaleTransform.ScaleXProperty, xBinding );

            Binding yBinding = new Binding( "Value" );
            yBinding.Source = range;
            BindingOperations.SetBinding( zoomTransform, ScaleTransform.ScaleYProperty, yBinding );


            if ( mindmapPanel.LayoutTransform != null )
            {
                TransformGroup group = new TransformGroup();
                group.Children.Add( mindmapPanel.LayoutTransform );
                group.Children.Add( zoomTransform );
                mindmapPanel.LayoutTransform = group;
            }
            else
            {
                mindmapPanel.LayoutTransform = zoomTransform;
            }

            range.ValueChanged += OnZoomChanged;
        }

        /// <summary>
        /// Gets or sets the zoom. The zoom values are relative to 1. This means that if you need 50% zoom set it to 0.5
        /// and if you need 200% zoom use 2.
        /// </summary>
        /// <value>
        /// The zoom value relative to 1. For example for 50% use 0.5 and for 200% use 2.0
        /// </value>
        public double Zoom
        {
            get { return range.Value; }
            set { range.Value = value; }
        }

        /// <summary>
        /// Moves the mind map view so the selected topic is in the center of the map.
        /// The movement is instant - no animation is used.
        /// </summary>
        /// <param name="topic">The topic to center.</param>
        public void CenterTopic( Topic topic )
        {
            CenterTopic( topic, false );
        }

        /// <summary>
        /// Moves the mind map view so the selected topic is in the center of the map.
        /// Depending on the <paramref name="useAnimation"/> parameter the movement is
        /// instant or slick (using an animation).
        /// </summary>
        /// <param name="topic">The topic to center.</param>
        /// <param name="useAnimation">if set to <c>true</c> the movement of the view will use a slick animation.</param>
        public void CenterTopic( Topic topic, bool useAnimation )
        {
            CenterTopicPresenter( mindmapPanel.FindTopicContentPresenter( topic.ID ), useAnimation );
        }

        //note: uninitialize may be needed
        public void InitializeDragBehavior()
        {
            mindmapPanel.PreviewMouseDown += OnMindMapPreviewMouseDown;
        }

        private void OnMindMapPreviewMouseDown( object sender, MouseButtonEventArgs e )
        {
            if ( e.Source == mindmapPanel )
            {
                startDragPoint = e.GetPosition( mindmapPanel );

                mindmapPanel.CaptureMouse();
                mindmapPanel.Cursor =
                    ( scrollViewer.ExtentWidth > scrollViewer.ViewportWidth ) ||
                    ( scrollViewer.ExtentHeight > scrollViewer.ViewportHeight )
                        ? Cursors.ScrollAll
                        : Cursors.Arrow;

                e.Handled = true;

                mindmapPanel.PreviewMouseMove += OnMindMapPreviewMouseMove;
                mindmapPanel.PreviewMouseUp += OnMindMapPreviewMouseUp;
            }
        }

        private void OnMindMapPreviewMouseMove( object sender, MouseEventArgs e )
        {
            if ( startDragPoint.HasValue && mindmapPanel.IsMouseCaptured )
            {
                Point mousePosition = e.GetPosition( mindmapPanel );

                scrollViewer.ScrollToHorizontalOffset(
                    scrollViewer.HorizontalOffset + startDragPoint.Value.X - mousePosition.X );
                scrollViewer.ScrollToVerticalOffset(
                    scrollViewer.VerticalOffset + startDragPoint.Value.Y - mousePosition.Y );

                e.Handled = true;
            }
        }

        private void OnMindMapPreviewMouseUp( object sender, MouseButtonEventArgs e )
        {
            if ( startDragPoint.HasValue )
            {
                startDragPoint = null;
                mindmapPanel.Cursor = Cursors.Arrow;
                mindmapPanel.ReleaseMouseCapture();
                e.Handled = true;

                mindmapPanel.PreviewMouseMove -= OnMindMapPreviewMouseMove;
                mindmapPanel.PreviewMouseUp -= OnMindMapPreviewMouseUp;
            }
        }

        private void CenterTopicPresenter( TopicContentPresenter presenter, bool useAnimation )
        {
            if ( presenter != null )
            {
                double horizontalOffset = 0;
                double verticalOffset = 0;
                if ( scrollViewer.ScrollableHeight > 0 )
                {
                    double centerX = presenter.X + presenter.ActualWidth / 2;
                    horizontalOffset = ( centerX * range.Value ) - ( scrollViewer.ActualWidth / 2 );
                }
                if ( scrollViewer.ScrollableWidth > 0 )
                {
                    double centerY = presenter.Y + presenter.ActualHeight / 2;
                    verticalOffset = ( centerY * range.Value ) - ( scrollViewer.ActualHeight / 2 );
                }

                if ( useAnimation )
                {
                    DoubleAnimation horizontalAnimation =
                        CreateMovingAnimation( scrollViewer.HorizontalOffset, horizontalOffset );
                    scrollViewer.BeginAnimation( HorizontalOffsetProperty, horizontalAnimation );

                    DoubleAnimation verticalAnimation =
                        CreateMovingAnimation( scrollViewer.VerticalOffset, verticalOffset );
                    scrollViewer.BeginAnimation( VerticalOffsetProperty, verticalAnimation );
                }
                else
                {
                    scrollViewer.ScrollToHorizontalOffset( horizontalOffset );
                    scrollViewer.ScrollToVerticalOffset( verticalOffset );
                }
            }
        }

        private static DoubleAnimation CreateMovingAnimation( double fromValue, double toValue )
        {
            DoubleAnimation anime = new DoubleAnimation( fromValue, toValue, TimeSpan.FromMilliseconds( 700 ) );
            anime.DecelerationRatio = 0.7;
            return anime;
        }

        private void OnZoomChanged( object sender, RoutedPropertyChangedEventArgs<double> e )
        {
            Topic selectedTopic = mindmapPanel.SelectedTopic ?? mindmapPanel.MainTopicPresenter.Topic;
            CenterTopic( selectedTopic, false );
        }
    }
}